﻿using System;
using System.Collections;
using System.Linq;
using System.Linq.Expressions;

namespace LightningTools.BCSTesterMan.Common.ExtensionMethods
{
    public static class IQueryableExtensions
    {
        public static Type GetDataItemType(this IQueryable dataSource)
        {
            Type dataSourceType = dataSource.GetType();
            Type dataItemType = typeof (object);

            if (dataSourceType.HasElementType)
            {
                dataItemType = dataSourceType.GetElementType();
            }
            else if (dataSourceType.IsGenericType)
            {
                dataItemType = dataSourceType.GetGenericArguments()[0];
            }
            else if (dataSource is IEnumerable)
            {
                IEnumerator dataEnumerator = dataSource.GetEnumerator();

                if (dataEnumerator.MoveNext() && dataEnumerator.Current != null)
                {
                    dataItemType = dataEnumerator.Current.GetType();
                }
            }

            return dataItemType;
        }

        public static int GetTotalRowCount(this IQueryable source)
        {
            Type dataSourceType = source.GetType();
            Type dataItemType = GetDataItemType(source);

            Type rowCounterType = typeof (IQueryableUtil<>).MakeGenericType(dataItemType);

            return (int)rowCounterType.GetMethod("Count", new[] {dataSourceType})
                .Invoke(null, new object[] {source});
        }

        public static IQueryable Contains(this IQueryable source, string propertyName, string searchClause)
        {
            Type dataSourceType = source.GetType();
            Type dataItemType = GetDataItemType(source);
            Type propertyType = dataItemType.GetProperty(propertyName).PropertyType;

            Type containsType = typeof (IQueryableUtil<,>).MakeGenericType(dataItemType, propertyType);

            return
                (IQueryable)containsType.GetMethod("Contains", new[] {dataSourceType, typeof (string), typeof (string)})
                    .Invoke(null, new object[] {source, propertyName, searchClause});
        }

        public static IQueryable Page(this IQueryable source, int pageSize, int currentPage)
        {
            Type dataSourceType = source.GetType();
            Type dataItemType = GetDataItemType(source);

            Type containsType = typeof (IQueryableUtil<>).MakeGenericType(dataItemType);

            var query = (IQueryable)containsType.GetMethod("Page", new[] {dataSourceType, typeof (int), typeof (int)})
                .Invoke(null, new object[] {source, pageSize, currentPage});
            return query;
        }

        public static IQueryable Sort(this IQueryable source, string propertyName, bool ascending)
        {
            Type dataSourceType = source.GetType();
            Type dataItemType = GetDataItemType(source);
            Type propertyType = dataItemType.GetProperty(propertyName).PropertyType;

            Type sorterType = typeof (IQueryableUtil<,>).MakeGenericType(dataItemType, propertyType);

            return (IQueryable)sorterType.GetMethod("Sort", new[] {dataSourceType, typeof (string), typeof (bool)})
                .Invoke(null, new object[] {source, propertyName, ascending});
        }

        #region Nested type: IQueryableUtil

        internal static class IQueryableUtil<T>
        {
            public static int Count(IQueryable source)
            {
                return source.OfType<T>().AsQueryable().Count();
            }

            public static IQueryable Page(IQueryable source, int pageSize, int pageIndex)
            {
                IQueryable query = source.OfType<T>().AsQueryable().Skip(pageSize * (pageIndex - 1)).Take(pageSize);
                return query;
            }
        }

        internal static class IQueryableUtil<T, PT>
        {
            public static IQueryable Sort(IQueryable source, string sortExpression, bool ascending)
            {
                ParameterExpression param = Expression.Parameter(typeof (T), "item");

                Expression<Func<T, PT>> sortLambda =
                    Expression.Lambda<Func<T, PT>>(
                        Expression.Convert(Expression.Property(param, sortExpression), typeof (PT)), param);

                if (ascending)
                {
                    // (o=> 0.sortExpression)
                    return source.OfType<T>().AsQueryable().OrderBy(sortLambda);
                }

                return source.OfType<T>().AsQueryable().OrderByDescending(sortLambda);
            }

            public static IQueryable Contains(IQueryable source, string propertyName, string searchClause)
            {
                ParameterExpression entityParam = Expression.Parameter(typeof (T), "item");

                //var containsLambda = Expression.Lambda<Func<T, PT>>(Expression.Convert(Expression.Property(param, PropertyName), typeof(PT)), param);

                MemberExpression memberExpression = Expression.Property(entityParam, propertyName);
                Expression convertExpression = Expression.Convert(memberExpression, typeof (object));
                ConstantExpression searchClauseParam = Expression.Constant(searchClause, typeof (string));

                MethodCallExpression containsExpression = Expression.Call(memberExpression, "Contains", new Type[] {},
                    new Expression[] {searchClauseParam});

                Expression<Func<T, bool>> containsLambda = Expression.Lambda<Func<T, bool>>(containsExpression,
                    entityParam);

                return source.OfType<T>().AsQueryable().Where(containsLambda);
            }
        }

        #endregion
    }
}